/******************************************************************************
*  Copyright(c) 2021-2023 Sietium
*
*  @file:    Window.h
*  @brif:    uniform interface
*
*  @date:    05/10/2021
*  @Author:  cary.Zhang
*
******************************************************************************/
#include <assert.h>
#include <sstream>

#include <perfglversion.h>
#include "window.h"
#include "node.h"

PerfWindow *PerfWindow::instanceWindow = NULL;

//int Window::majorVersion = 3;
//int Window::minorVersion = 0;
int PerfWindow::glVersion = 300;

bool PerfWindow::fullScreen = false;
bool PerfWindow::msaaFlag = false;
int PerfWindow::windowWidth = 800;
int PerfWindow::windowHeight = 600;

bool PerfWindow::isOpenGLSupported()
{
#ifndef USE_GLES
    checBestGLVersion();
#endif

    if (!glfwInit()) {
        fprintf(stderr, "Failed to initialize GLFW\n");
        return false;
    }
    glfwWindowHint(GLFW_VISIBLE, GL_FALSE);

#ifdef USE_GLES
    glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_ES_API);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
    glfwWindowHint(GLFW_CONTEXT_CREATION_API, GLFW_EGL_CONTEXT_API);

    glfwWindowHint(GLFW_ALPHA_BITS, 0);

    /* When using OpenGL ES, use fake GL version as 4.3 */
    PerfGLVersionManager::major = 4;
    PerfGLVersionManager::minor = 3;
    glVersion = 430;
#else
    if (PerfGLVersionManager::major * 10 + PerfGLVersionManager::minor >= 33) {
        glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, PerfGLVersionManager::major);
        glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, PerfGLVersionManager::minor);
        glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    } else {
        glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_ANY_PROFILE);
    }
#endif
    GLFWwindow *window = glfwCreateWindow(200, 200, "opengl version", NULL, NULL);
    if (!window) {
        fprintf(stderr, "failed to open window\n");
        return false;
    }

    glfwMakeContextCurrent(window);
    glfwHideWindow(window);

#ifdef USE_GLES
    gladLoadGLES2(glfwGetProcAddress);
    glfwSwapInterval(0);

    glVersion = 430;
#else
    gladLoadGL(glfwGetProcAddress);

    const unsigned char *version = glGetString(GL_VERSION);
    if (!version || strlen((char *)version) < 3) {
        printf("System OpenGL Version Get Failed !!!!\n");
        return false;
    }

    if (version[1] != '.') {
        printf("Invalid OpenGL Version String\n");
        return false;
    }

    glVersion = (version[0] - '0') * 100 + (version[2] - '0') * 10;

    if (glVersion < 330) {
        //printf("GL Version is %d.%d, The minimum GL version required for GPU-perf is 3.3 !\n",
        //       version[0] - '0', version[2] - '0');
        //return false;
        printf("warning! GL Version is %d.%d, The GL version less than 3.3 !\n",
               version[0] - '0', version[2] - '0');
    }
#endif
    glfwDestroyWindow(window);
    glfwTerminate();
    return true;
}

PerfWindow::PerfWindow()
{
    width = 800;
    height = 600;

    if (!create()) {
        Log::error("Create Window Error");
    }

    std::stringstream ss;

    ss << "    OpenGL Information" << std::endl;
    ss << "    GL_VENDOR:     " << glGetString(GL_VENDOR) << std::endl;
    ss << "    GL_RENDERER:   " << glGetString(GL_RENDERER) << std::endl;
    ss << "    GL_VERSION:    " << glGetString(GL_VERSION) << std::endl;

    Log::info("%s", ss.str().c_str());
    Log::info("=======================================================================\n");
}

PerfWindow::~PerfWindow()
{
    if (!destory()) {
        Log::error("Destory Window Error");
    }
}

void PerfWindow::onWindowResized(GLFWwindow *window, int width, int height)
{
    (void)window;
    get()->setWindowSize(width, height);
}

PerfWindow *PerfWindow::get()
{
    if (instanceWindow == NULL)
        instanceWindow = new PerfWindow();

    return instanceWindow;
}

void PerfWindow::swapBuffer()
{
    glfwSwapBuffers(glfwWindow);
    glfwPollEvents();
}

bool PerfWindow::processInput()
{
    if (glfwGetKey(glfwWindow, GLFW_KEY_ESCAPE) == GLFW_PRESS ||
            glfwWindowShouldClose(glfwWindow)) {
        glfwSetWindowShouldClose(glfwWindow, true);
        return false;
    }
    return true;
}

void PerfWindow::setRenderNode(Node *node)
{
    if (node != renderNode && node) {
        renderNode = node;
        setWindowSize(width, height);
    }
}

void PerfWindow::setWindowSize(int w, int h)
{
    width = w;
    height = h;
    if (renderNode)
        renderNode->onResized(w, h);
}

void PerfWindow::setWindowTitle(const char *title)
{
    glfwSetWindowTitle(glfwWindow, title);
}

void PerfWindow::setWindowMsaa(bool enable)
{
    msaaFlag = enable;
}


bool PerfWindow::create()
{
    if (glfwWindow != NULL)
        return true;

    if (!glfwInit()) {
        fprintf(stderr, "failed to initialize GLFW\n");
        return false;
    }

#ifdef USE_GLES
    if (msaaFlag == true) {
        glfwWindowHint(GLFW_SAMPLES, 16);
    }

    glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_ES_API);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
    glfwWindowHint(GLFW_CONTEXT_CREATION_API, GLFW_EGL_CONTEXT_API);

    /* ALPHA_BITS is set to 0 here manually to fix a bug on some GLFW versions with wayland backend.
     * See GLFW commit 6281f49 fixed it. */
    glfwWindowHint(GLFW_ALPHA_BITS, 0);
#else
    glfwWindowHint(GLFW_SAMPLES, 16);

    if (glVersion >= 330) {
        glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, glVersion / 100);
        glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, (glVersion / 10) % 10);
        glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    } else {
        glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_ANY_PROFILE);
    }
#endif

    glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);
    int desktopWidth = width;
    int desktopHeight = height;

    int monitorCount;
    GLFWmonitor *monitor = nullptr;
    GLFWmonitor **pMonitor =  glfwGetMonitors(&monitorCount);

    for (int i = 0; i < monitorCount; i++) {
        const GLFWvidmode *mode = glfwGetVideoMode(pMonitor[i]);
        desktopWidth = mode->width;
        desktopHeight = mode->height;
        monitor = pMonitor[i];
        break;
    }

    if (fullScreen) {
        width = desktopWidth;
        height = desktopHeight;
    }

    glfwWindow = glfwCreateWindow(width, height, "GPU-perf", fullScreen ? monitor : nullptr, nullptr);
    if (!glfwWindow) {
        fprintf(stderr, "failed to open window\n");
        return false;
    }

    if (!fullScreen) {
        int x = (desktopWidth - width) >> 1;
        int y = (desktopHeight - height) >> 1;
        glfwSetWindowPos(glfwWindow, x, y);
    }

    glfwSetWindowSizeCallback(glfwWindow, &PerfWindow::onWindowResized);
    glfwMakeContextCurrent(glfwWindow);

#ifdef USE_GLES
    gladLoadGLES2(glfwGetProcAddress);
    glfwSwapInterval(0);
#else
    gladLoadGL(glfwGetProcAddress);

    glDisable(GL_MULTISAMPLE);
#endif

    return true;
}

bool PerfWindow::destory()
{
    if (glfwWindow != NULL) {
        glfwDestroyWindow(glfwWindow);
        glfwTerminate();
        glfwWindow = NULL;
    }

    return true;
}

void PerfWindow::checBestGLVersion()
{
    /*glfw find best version*/
    GLint glversions[][2]= {
        {4, 6},
        {4, 5},
        {4, 4},
        {4, 3},
        {4, 2},
        {4, 1},
        {4, 0},

        {3, 3},
    };
    bool isFinded = false;
    int versionCount = sizeof(glversions) / (2 * sizeof(GLint));
    for (int i = 0; i < versionCount; i++) {
        if (!glfwInit()) {
            fprintf(stderr, "Failed to initialize GLFW\n");
            return;
        }

        glfwWindowHint(GLFW_VISIBLE, GL_FALSE);
        glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, glversions[i][0]);
        glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, glversions[i][1]);
        int tempVersion = glversions[i][0] *10 + glversions[i][1];
        if (tempVersion >= 33) {
            glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
        } else {
            glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_ANY_PROFILE);
        }

        GLFWwindow *window = glfwCreateWindow(200, 200, "opengl version", NULL, NULL);
        if (window) {
            PerfGLVersionManager::major = glversions[i][0];
            PerfGLVersionManager::minor = glversions[i][1];
            glfwDestroyWindow(window);
            window = nullptr;
            isFinded = true;
        }
        glfwTerminate();
        if (isFinded) {
            break;
        }
    }
}
